﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Flower.Processing.Activities;
using Flower.Workflow;
using System.Transactions;
using Flower.Processing.Interruptions;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Text;

namespace Flower.Processing
{
    internal class ProcessBuilder : IProcessBuilder
    {
        private enum ScopeType
        {
            If,
            Else,
            While,
            For,
            ForEach,
            TryCatch,
            Wait,
            Lock,
            Named
        }

        private class Scope
        {
            public ScopeType Type;
            public int Start;
            public int ForwardJumpsStart;
        }

        private class NamedScope : Scope
        {
            public string Name;
        }

        private abstract class ContainerScope : Scope
        {
            public readonly Stack<Scope> NestedScopes;

            protected ContainerScope()
            {
                NestedScopes = new Stack<Scope>();
            }
        }

        private class LoopScope : ContainerScope
        {
        }

        private class TryCatchScope : ContainerScope
        {
        }

        private class LockScope : Scope
        {
            internal Func<IContext, IEnumerable<string>> Resources;
            internal Func<IContext, string> Owner;
        }

        private readonly bool _emitBreakpoints;
        private readonly Process _process;
        private readonly Stack<Func<IContext, string>> _forEachSubsets = new Stack<Func<IContext, string>>();
        private readonly Stack<Jump> _elseIfJumps = new Stack<Jump>();
        private readonly Stack<Jump> _breakJumps = new Stack<Jump>();
        private readonly Stack<Jump> _finallyJumps = new Stack<Jump>();
        private readonly Stack<Jump> _waitJumps = new Stack<Jump>();
        private readonly Stack<Scope> _scopes = new Stack<Scope>();
        private readonly Stack<LoopScope> _loopScopes = new Stack<LoopScope>();
        private readonly Stack<TryCatchScope> _tryCatchScopes = new Stack<TryCatchScope>();
        private readonly Stack<Scope> _waitScopes = new Stack<Scope>();
        private readonly Stack<Action<IContext>> _forLoopActions = new Stack<Action<IContext>>();
        private readonly List<IActivity> _activities = new List<IActivity>();
        private readonly Dictionary<string, int> _statementCounters = new Dictionary<string, int>(); 
        private string _lastName;

        public List<IActivity> Activities { get { return _activities; } }

        public ProcessBuilder(Process process, bool emitBreakpoints)
        {
            _process = process;
            _emitBreakpoints = emitBreakpoints;
        }

        public void CheckEndState()
        {
            if (_scopes.Count > 0)
            {
                throw new ProcessBuilderException("Unexpected end of process.");
            }
        }

        #region Scopes nesting

        private void PushScope(Scope scope)
        {
            _scopes.Push(scope);

            if (_loopScopes.Count > 0)
            {
                _loopScopes.Peek().NestedScopes.Push(scope);
            }

            if (_tryCatchScopes.Count > 0)
            {
                _tryCatchScopes.Peek().NestedScopes.Push(scope);
            }
        }

        private Scope PopScope()
        {
            Scope scope = _scopes.Pop();

            if (_loopScopes.Count > 0 && _loopScopes.Peek() != scope)
            {
                _loopScopes.Peek().NestedScopes.Pop();
            }

            if (_tryCatchScopes.Count > 0 && _tryCatchScopes.Peek() != scope)
            {
                _tryCatchScopes.Peek().NestedScopes.Pop();
            }

            return scope;
        }

        private void UnrollScopes(ContainerScope containerScope, string statement, int statementIndex)
        {
            foreach (Scope scope in containerScope.NestedScopes.Reverse())
            {
                var lockScope = scope as LockScope;
                if (lockScope != null)
                {
                    _activities.Add
                    (
                        AssignNameAndLocation
                        (
                            new Unlock
                            {
                                Resources = lockScope.Resources,
                                Owner = lockScope.Owner
                            },
                            statement,
                            statementIndex
                        )
                    );
                    continue;
                }

                if (scope.Type == ScopeType.ForEach)
                {
                    _activities.Add
                    (
                        AssignNameAndLocation
                        (
                            new Execute 
                            {
                                Action = 
                                    ctx => 
                                        _process
                                            .__ForEachData
                                            .RemoveAt(_process.__ForEachData.Count - 1)
                            }, 
                            statement,
                            statementIndex
                        )
                    );
                }
            }
        }

        #endregion

        #region Naming

        private int GetStatementIndex(string statement)
        {
            int statementIndex;
            if (_statementCounters.TryGetValue(statement, out statementIndex))
            {
                ++statementIndex;
            }
            else
            {
                statementIndex = 1;
            }

            _statementCounters[statement] = statementIndex;

            return statementIndex;
        }

        private string GetScopePrefix()
        {
            var bld = new StringBuilder();

            foreach (Scope scope in _scopes)
            {
                var namedScope = scope as NamedScope;

                if (namedScope != null)
                {
                    bld
                        .Append(namedScope.Name)
                        .Append(" : ");
                }
            }

            return bld.ToString();
        }

        private Activity AssignNameAndLocation(Activity activity, string statement, int statementIndex)
        {
            if (_lastName != null)
            {
                string activityName = GetScopePrefix() + _lastName;

                if (_activities.Any(act => act.Name == activityName))
                {
                    throw new ProcessBuilderException(string.Format("Activity with the name '{0}' already exists.", activityName));
                }

                activity.Name = activityName;
                _lastName = null;
            }
            else
            {
                activity.Name =
                    string.Format
                    (
                        "{0}{1}[{2}]({3})",
                        GetScopePrefix(),
                        statement,
                        statementIndex,
                        activity.GetType().Name
                    );
            }

            activity.Location = new ActivityLocation
            {
                StatementIndex = statementIndex,
                StatementType = statement
            };

            return activity;
        }

        public IProcessBuilder Name(string name)
        {
            if (name == null) throw new ArgumentNullException("name");

            _lastName = name;
            return this;
        }

        public IProcessBuilder Begin(string name)
        {
            if (name == null) throw new ArgumentNullException("name");

            PushScope
            (
                new NamedScope
                {
                    Type = ScopeType.Named,
                    Name = name
                }
            );

            return this;
        }

        #endregion

        #region Flow control

        public IProcessBuilder If(Expression<Func<IContext, bool>> condition)
        {
            if (condition == null) throw new ArgumentNullException("condition");

            PushScope
            (
                new Scope 
                {
                    ForwardJumpsStart = _elseIfJumps.Count, 
                    Type = ScopeType.If, 
                    Start = _activities.Count
                }
            );
            _activities.Add(AssignNameAndLocation(new ConditionalJump { Condition = condition.Compile() }, "If", GetStatementIndex("If")));
            return this;
        }

        public IProcessBuilder Else()
        {
            if (_activities.Count == 0)
            {
                throw new ProcessBuilderException("Process cannot start with Else.");
            }

            if (_scopes.Count == 0)
            {
                throw new ProcessBuilderException(string.Format("Unexpected Else after {0}.", _activities[_activities.Count - 1].Name));
            }

            Scope scope = PopScope();

            if (scope.Type != ScopeType.If)
            {
                throw new ProcessBuilderException(string.Format("Unexpected Else after {0}.", _activities[_activities.Count - 1].Name));
            }

            int statementIndex = GetStatementIndex("Else");

            _activities.Add(AssignNameAndLocation(new End(), "Else", statementIndex));

            PushScope
            (
                new Scope 
                {
                    ForwardJumpsStart = scope.ForwardJumpsStart,
                    Type = ScopeType.Else, 
                    Start = _activities.Count
                }
            );

            var jump = new Jump();
            _elseIfJumps.Push(jump);
            _activities.Add(AssignNameAndLocation(jump, "Else", statementIndex));

            ((ConditionalJump)_activities[scope.Start]).Position = _activities.Count;

            return this;
        }

        public IProcessBuilder ElseIf(Expression<Func<IContext, bool>> condition)
        {
            if (condition == null) throw new ArgumentNullException("condition");

            if (_activities.Count == 0)
            {
                throw new ProcessBuilderException("Process cannot start with ElseIf.");
            }

            Scope scope = PopScope();

            if (scope.Type != ScopeType.If)
            {
                throw new ProcessBuilderException(string.Format("Unexpected ElseIf after {0}.", _activities[_activities.Count - 1].Name));
            }

            int statementIndex = GetStatementIndex("ElseIf");

            _activities.Add(AssignNameAndLocation(new End(), "ElseIf", statementIndex));

            var jump = new Jump();
            _elseIfJumps.Push(jump);
            _activities.Add(AssignNameAndLocation(jump, "ElseIf", statementIndex));

            PushScope
            (
                new Scope
                {
                    ForwardJumpsStart = scope.ForwardJumpsStart,
                    Type = ScopeType.If,
                    Start = _activities.Count
                }
            );

            ((ConditionalJump)_activities[scope.Start]).Position = _activities.Count;
            _activities.Add(AssignNameAndLocation(new ConditionalJump { Condition = condition.Compile() }, "ElseIf", statementIndex));

            return this;
        }

        public IProcessBuilder While(Expression<Func<IContext, bool>> condition)
        {
            if (condition == null) throw new ArgumentNullException("condition");

            var scope = new LoopScope
            {
                ForwardJumpsStart = _breakJumps.Count,
                Type = ScopeType.While,
                Start = _activities.Count
            };
            PushScope(scope);
            _loopScopes.Push(scope);
            _activities.Add(AssignNameAndLocation(new ConditionalJump { Condition = condition.Compile() }, "While", GetStatementIndex("While")));
            return this;
        }

        public IProcessBuilder For(Action<IContext> init, Expression<Func<IContext, bool>> condition, Action<IContext> loop)
        {
            if (init == null) throw new ArgumentNullException("init");
            if (condition == null) throw new ArgumentNullException("condition");
            if (loop == null) throw new ArgumentNullException("loop");

            int statementIndex = GetStatementIndex("For");
            _activities.Add(AssignNameAndLocation(new Execute { Action = init }, "For", statementIndex));
            var scope = new LoopScope
            {
                ForwardJumpsStart = _breakJumps.Count,
                Type = ScopeType.For,
                Start = _activities.Count
            };
            PushScope(scope);
            _loopScopes.Push(scope);
            _activities.Add(AssignNameAndLocation(new ConditionalJump { Condition = condition.Compile() }, "For", statementIndex));
            _forLoopActions.Push(loop);
            return this;
        }

        public IProcessBuilder Break()
        {
            if (_activities.Count == 0)
            {
                throw new ProcessBuilderException("Process cannot start with Break.");
            }

            if (_loopScopes.Count == 0)
            {
                throw new ProcessBuilderException(string.Format("Break outside a loop after {0}.", _activities[_activities.Count - 1].Name));
            }

            int statementIndex = GetStatementIndex("Break");

            LoopScope loopScope = _loopScopes.Peek();

            UnrollScopes(loopScope, "Break", statementIndex);
            _activities.Add
            (
                AssignNameAndLocation
                (
                    new End(),
                    "Break",
                    statementIndex
                )
            );

            var jump = new Jump();
            _breakJumps.Push(jump);
            _activities.Add(AssignNameAndLocation(jump, "Break", statementIndex));
            return this;
        }

        public IProcessBuilder Continue()
        {
            if (_activities.Count == 0)
            {
                throw new ProcessBuilderException("Process cannot start with Continue.");
            }

            if (_loopScopes.Count == 0)
            {
                throw new ProcessBuilderException(string.Format("Continue outside a loop after {0}.", _activities[_activities.Count - 1].Name));
            }

            int statementIndex = GetStatementIndex("Continue");

            LoopScope loopScope = _loopScopes.Peek();

            UnrollScopes(loopScope, "Continue", statementIndex);
            _activities.Add
            (
                AssignNameAndLocation
                (
                    new End(),
                    "Continue",
                    statementIndex
                )
            );

            _activities.Add(AssignNameAndLocation(new Jump { Position = loopScope.Start }, "Continue", statementIndex));
            return this;
        }

        public IProcessBuilder End()
        {
            if (_activities.Count == 0)
            {
                throw new ProcessBuilderException("Process cannot start with End.");
            }

            if (_scopes.Count == 0)
            {
                throw new ProcessBuilderException(string.Format("Unexpected End after {0}.", _activities[_activities.Count - 1].Name));
            }

            int statementIndex = GetStatementIndex("End");

            Scope scope = PopScope();

            _activities.Add(AssignNameAndLocation(new End(), "End", statementIndex));

            if (scope.Type == ScopeType.For)
            {
                _activities.Add(AssignNameAndLocation(new Execute { Action = _forLoopActions.Pop() }, "End", statementIndex));
            }

            if (scope.Type == ScopeType.While || 
                scope.Type == ScopeType.For || 
                scope.Type == ScopeType.ForEach)
            {
                _activities.Add(AssignNameAndLocation(new Jump { Position = scope.Start }, "End", statementIndex));
                _loopScopes.Pop();
            }

            switch (scope.Type)
            {
                case ScopeType.If:
                case ScopeType.ForEach:
                case ScopeType.For:
                case ScopeType.While:
                    ((Jump)_activities[scope.Start]).Position = _activities.Count;
                    break;
                case ScopeType.Lock:
                    {
                        var lockScope = (LockScope)scope;
                        _activities.Add
                            (
                                AssignNameAndLocation
                                    (
                                        new Unlock
                                        {
                                            Resources = lockScope.Resources,
                                            Owner = lockScope.Owner
                                        },
                                        "End",
                                        statementIndex
                                    )
                            );

                        ((Jump)_activities[scope.Start]).Position = _activities.Count;
                    }
                    break;
                case ScopeType.TryCatch:
                    _tryCatchScopes.Pop();
                    break;
                case ScopeType.Wait:
                    _activities.Add
                        (
                            AssignNameAndLocation
                                (
                                    new InterruptJump 
                                    {
                                        Position = scope.Start,
                                        Interruption = Waiting.Instance
                                    },
                                    "End",
                                    statementIndex
                                )
                        );
                    _waitScopes.Pop();
                    break;
            }

            Stack<Jump> jumps = null;

            switch (scope.Type)
            {
                case ScopeType.If:
                case ScopeType.Else:
                    jumps = _elseIfJumps;
                    break;
                case ScopeType.While:
                case ScopeType.For:
                case ScopeType.ForEach:
                    jumps = _breakJumps;
                    break;
                case ScopeType.TryCatch:
                    jumps = _finallyJumps;
                    break;
                case ScopeType.Wait:
                    jumps = _waitJumps;
                    break;
            }

            if (jumps != null)
            {
                while (jumps.Count > scope.ForwardJumpsStart)
                {
                    jumps.Pop().Position = _activities.Count;
                }
            }

            switch (scope.Type)
            {
                case ScopeType.ForEach:
                    _forEachSubsets.Pop();
                    break;
                case ScopeType.Wait:
                    _activities.Add(AssignNameAndLocation(new EraseWaitBackLinks(), "End", statementIndex));
                    break;
            }

            return this;
        }

        #endregion

        #region ForEach

        public IProcessBuilder ForEach<T>(Expression<Func<IContext, string>> subset, Expression<Func<IContext, MessageProperty>> orderBy, Expression<Func<IContext, SortOrder>> order, Expression<Func<IContext, int>> batchSize, Expression<Func<IContext, bool>> wait, Action<string, T> item)
        {
            if (subset == null) throw new ArgumentNullException("subset");
            if (orderBy == null) throw new ArgumentNullException("orderBy");
            if (order == null) throw new ArgumentNullException("order");
            if (batchSize == null) throw new ArgumentNullException("batchSize");
            if (wait == null) throw new ArgumentNullException("wait");
            if (item == null) throw new ArgumentNullException("item");

            int statementIndex = GetStatementIndex("ForEach");

            var subsetCompiled = subset.Compile();

            var forEach =
                new ForEach<T>
                {
                    Subset = subsetCompiled,
                    BatchSize = batchSize.Compile(),
                    Order = order.Compile(),
                    OrderBy = orderBy.Compile(),
                    Wait = wait.Compile(),
                    Item = item
                };

            Exec
            (
                "ForEach", 
                statementIndex, 
                ctx => 
                    _process.__ForEachData.Add(new Process.ForEachData())
            );

            AssignNameAndLocation
            (
                forEach, 
                "ForEach", 
                statementIndex
            );

            _forEachSubsets.Push(subsetCompiled);

            var scope = new LoopScope
            {
                ForwardJumpsStart = _breakJumps.Count,
                Type = ScopeType.ForEach,
                Start = _activities.Count
            };
            PushScope(scope);
            _loopScopes.Push(scope);
            _activities.Add(forEach);
            return this;
        }

        public IProcessBuilder RemoveCurrent()
        {
            _activities.Add
            (
                AssignNameAndLocation
                (
                    new RemoveCurrent(),
                    "RemoveCurrent",
                     GetStatementIndex("RemoveCurrent")
                )
            );

            return this;
        }

        public IProcessBuilder UpdateCurrent<T>(Expression<Func<IContext, string>> name, Expression<Func<IContext, T>> message, Expression<Func<IContext, MessageFormat>> format)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (message == null) throw new ArgumentNullException("message");
            if (format == null) throw new ArgumentNullException("format");

            _activities.Add
            (
                AssignNameAndLocation
                (
                    new UpdateCurrent<T> 
                    {
                        MessageName = name.Compile(),
                        Subset = _forEachSubsets.Peek(),
                        Message = message.Compile(),
                        Format = format.Compile()
                    },
                    "UpdateCurrent",
                    GetStatementIndex("UpdateCurrent")
                )
            );

            return this;
        }

        #endregion

        #region Exceptions handling

        public IProcessBuilder Try()
        {
            var scope = new TryCatchScope 
            {
                ForwardJumpsStart = _finallyJumps.Count,
                Type = ScopeType.TryCatch, 
                Start = _activities.Count 
            };

            PushScope(scope);
            _tryCatchScopes.Push(scope);

            return this;
        }

        public IProcessBuilder Catch<TException>(Action<IContext, TException> handler) where TException : Exception
        {
            if (handler == null) throw new ArgumentNullException("handler");

            if (_activities.Count == 0)
            {
                throw new ProcessBuilderException("Process cannot start with Catch.");
            }

            if (_tryCatchScopes.Count == 0)
            {
                throw new ProcessBuilderException(string.Format("Unexpected Catch after {0}.", _activities[_activities.Count - 1].Name));
            }

            int statementIndex = GetStatementIndex("Catch");

            var jump = new Jump();
            _finallyJumps.Push(jump);
            _activities.Add(AssignNameAndLocation(jump, "Catch", statementIndex));

            int catchPos = _activities.Count;

            UnrollScopes(_tryCatchScopes.Peek(), "Catch", statementIndex);

            var scope = _tryCatchScopes.Peek();

            var exceptionContainer = new ExceptionContainer
            {
                ExceptionType = typeof(TException),
                HandlerPosition = catchPos
            };

            for (int i = scope.Start; i < _activities.Count; i++)
            {
                ((Activity)_activities[i]).ExceptionContainers.Add(exceptionContainer);
            }

            _activities.Add
            (
                AssignNameAndLocation
                (
                    new Catch<TException>
                    {
                        ExceptionContainer = exceptionContainer,
                        Handler = handler
                    },
                    "Catch",
                    statementIndex
                )
            );

            return this;
        }

        public IProcessBuilder Finally()
        {
            if (_activities.Count == 0)
            {
                throw new ProcessBuilderException("Process cannot start with Finally.");
            }

            var scope = _scopes.Peek();

            if (scope.Type != ScopeType.TryCatch)
            {
                throw new ProcessBuilderException(string.Format("Unexpected Finally after {0}.", _activities[_activities.Count - 1].Name));
            }

            while (_finallyJumps.Count > scope.ForwardJumpsStart)
            {
                _finallyJumps.Pop().Position = _activities.Count;
            }

            return this;
        }

        public IProcessBuilder Throw<TException>(Expression<Func<IContext, TException>> exception) where TException : Exception
        {
            if (exception == null) throw new ArgumentNullException("exception");

            return Exec(ctx => { throw exception.Compile()(ctx); });
        }

        #endregion

        #region Execs

        public IProcessBuilder Exec(Action<IContext> action)
        {
            if (action == null) throw new ArgumentNullException("action");

            return Exec("Exec", GetStatementIndex("Exec"), action);
        }

        private IProcessBuilder Exec(string statement, int statementIndex, Action<IContext> action)
        {
            _activities.Add(AssignNameAndLocation(new Execute { Action = action }, statement, statementIndex));
            return this;
        }

        public IProcessBuilder ExecAsync(Func<IContext, Task> action)
        {
            _activities.Add(AssignNameAndLocation(new ExecuteAsync { Action = action }, "ExecAsync", GetStatementIndex("ExecAsync")));
            return this;
        }

        public IProcessBuilder ExecAndSave(Action<IContext> action)
        {
            if (action == null) throw new ArgumentNullException("action");

            _activities.Add(AssignNameAndLocation(new ExecuteAndSave { Action = action }, "ExecAndSave", GetStatementIndex("ExecAndSave")));
            return this;
        }

        public IProcessBuilder Invoke<TSvc>(Expression<Func<IContext, string>> servicePath, Action<IContext, TSvc> call, ExecutionFlags flags = ExecutionFlags.None)
        {
            if (servicePath == null) throw new ArgumentNullException("servicePath");
            if (call == null) throw new ArgumentNullException("call");

            _activities.Add
            (
                AssignNameAndLocation
                (
                    new Invoke<TSvc>(flags) 
                    {
                        ServicePath = servicePath.Compile(),
                        Call = call
                    },
                    "Invoke",
                    GetStatementIndex("Invoke")
                )
            );
            return this;
        }

        public IProcessBuilder Invoke<TSvc>(TSvc service, Action<IContext, TSvc> call, ExecutionFlags flags = ExecutionFlags.None)
        {
            if (call == null) throw new ArgumentNullException("call");

            _activities.Add
            (
                AssignNameAndLocation
                (
                    new Invoke<TSvc>(flags)
                    {
                        Service = service,
                        Call = call
                    },
                    "Invoke",
                    GetStatementIndex("Invoke")
                )
            );
            return this;
        }

    	public IProcessBuilder StartProcess<TIn, TOut>(Expression<Func<IContext, string>> workflowPath, Expression<Func<IContext, TIn>> arg, Action<IContext, string, TOut> result)
        {
    	    if (workflowPath == null) throw new ArgumentNullException("workflowPath");
    	    if (arg == null) throw new ArgumentNullException("arg");
    	    if (result == null) throw new ArgumentNullException("result");

    	    _activities.Add
            (
                AssignNameAndLocation
                (
                    new StartProcess<TIn, TOut>
                    {
                        WorkflowPath = workflowPath.Compile(),
                        Arg = arg.Compile(),
                        Result = result
                    },
                    "StartProcess",
                    GetStatementIndex("StartProcess")
                )
            );
            return this;
        }

        #endregion

        #region Waiting

        public IProcessBuilder WaitAny()
        {
            var scope = new Scope
            {
                ForwardJumpsStart = _waitJumps.Count,
                Type = ScopeType.Wait,
                Start = _activities.Count
            };

            PushScope(scope);
            _waitScopes.Push(scope);

            int statementIndex = GetStatementIndex("WaitAny");

            _activities.Add(AssignNameAndLocation(new EraseWaitBackLinks(), "WaitAny", statementIndex));
            return this;
        }

        public IProcessBuilder JoinProcess(Expression<Func<IContext, string>> pid, Action<IContext> waitingFinished)
        {
            if (pid == null) throw new ArgumentNullException("pid");

            bool isInWaitScope = _waitScopes.Count > 0;
            var wait = new JoinProcess
            {
                Pid = pid.Compile(),
                WaitingFinished = waitingFinished,
                Interrupt = !isInWaitScope
            };

            if (isInWaitScope)
            {
                _waitJumps.Push(wait);
            }

            _activities.Add(AssignNameAndLocation(wait, "JoinProcess", GetStatementIndex("JoinProcess")));
            wait.Position = _activities.Count;
            return this;
        }

        public IProcessBuilder Enqueue<TMsg>(Expression<Func<IContext, TMsg>> message, Expression<Func<IContext, MessageFormat>> format)
        {
            return Enqueue(null, null, message, format, null);
        }

        public IProcessBuilder Enqueue<TMsg>(Expression<Func<IContext, string>> subset, Expression<Func<IContext, TMsg>> message, Expression<Func<IContext, MessageFormat>> format, Action<IContext> waitingFinished)
        {
            return Enqueue(subset, null, message, format, waitingFinished);
        }

        public IProcessBuilder Enqueue<TMsg>(Expression<Func<IContext, string>> subset, Expression<Func<IContext, string>> messageName, Expression<Func<IContext, TMsg>> message, Expression<Func<IContext, MessageFormat>> format, Action<IContext> waitingFinished)
        {
            if (message == null) throw new ArgumentNullException("message");
            if (format == null) throw new ArgumentNullException("format");

            bool isInWaitScope = _waitScopes.Count > 0;
            var wait = new Enqueue<TMsg>
            {
                Subset = (subset != null) ? subset.Compile() : null,
                MessageName = (messageName != null) ? messageName.Compile() : null,
                Message = message.Compile(),
                Format = format.Compile(),
                WaitingFinished = waitingFinished,
                Interrupt = !isInWaitScope
            };

            if (isInWaitScope)
            {
                _waitJumps.Push(wait);
            }

            _activities.Add(AssignNameAndLocation(wait, "Enqueue", GetStatementIndex("Enqueue")));
            wait.Position = _activities.Count;
            return this;
        }

        public IProcessBuilder Dequeue<TMsg>(Expression<Func<IContext, string>> subset, Action<IContext, TMsg> message)
        {
            if (message == null) throw new ArgumentNullException("message");

            bool isInWaitScope = _waitScopes.Count > 0;
            var wait = new Dequeue<TMsg>
            {
                Subset = (subset != null) ? subset.Compile() : null,
                Message = message,
                Interrupt = !isInWaitScope
            };

            if (isInWaitScope)
            {
                _waitJumps.Push(wait);
            }

            _activities.Add(AssignNameAndLocation(wait, "Dequeue", GetStatementIndex("Dequeue")));
            wait.Position = _activities.Count;
            return this;
        }

        public IProcessBuilder Dequeue<TMsg>(Action<IContext, TMsg> message)
        {
            return Dequeue(null, message);
        }

        public IProcessBuilder WaitUntil(Expression<Func<IContext, DateTime>> moment, Action<IContext> waitingFinished)
        {
            if (moment == null) throw new ArgumentNullException("moment");

            bool isInWaitScope = _waitScopes.Count > 0;
            var wait = new WaitUntil
            {
                Moment = moment.Compile(),
                Interrupt = !isInWaitScope,
                WaitingFinished = waitingFinished
            };

            if (isInWaitScope)
            {
                _waitJumps.Push(wait);
            }

            _activities.Add(AssignNameAndLocation(wait, "WaitUntil", GetStatementIndex("WaitUntil")));
            wait.Position = _activities.Count;
            return this;
        }

        #endregion

        #region Locking

        public IProcessBuilder Lock
        (
            Expression<Func<IContext, IEnumerable<string>>> resources, 
            Expression<Func<IContext, string>> owner, 
            Expression<Func<IContext, bool>> wait, 
            Action<IContext, LockFailure[]> result
        )
        {
            if (resources == null) throw new ArgumentNullException("resources");
            if (owner == null) throw new ArgumentNullException("owner");
            if (wait == null) throw new ArgumentNullException("wait");

            bool isInWaitScope = _waitScopes.Count > 0;
            var lck = new Lock
            {
                Resources = resources.Compile(),
                Owner = owner.Compile(),
                Wait = wait.Compile(),
                Interrupt = !isInWaitScope,
                Result = result
            };

            if (isInWaitScope)
            {
                _waitJumps.Push(lck);
            }

            _activities.Add(AssignNameAndLocation(lck, "Lock", GetStatementIndex("Lock")));
            lck.Position = _activities.Count;
            return this;
        }

        public IProcessBuilder Unlock(Expression<Func<IContext, IEnumerable<string>>> resources, Expression<Func<IContext, string>> owner)
        {
            if (resources == null) throw new ArgumentNullException("resources");
            if (owner == null) throw new ArgumentNullException("owner");

            _activities.Add
            (
                AssignNameAndLocation
                (
                    new Unlock
                    {
                        Resources = resources.Compile(),
                        Owner = owner.Compile()
                    },
                    "Unlock",
                    GetStatementIndex("Unlock")
                )
            );
            return this;
        }

        public IProcessBuilder BeginLock
        (
            Expression<Func<IContext, IEnumerable<string>>> resources, 
            Expression<Func<IContext, string>> owner, 
            Expression<Func<IContext, bool>> wait, 
            Action<IContext, LockFailure[]> result
        )
        {
            if (resources == null) throw new ArgumentNullException("resources");
            if (owner == null) throw new ArgumentNullException("owner");
            if (wait == null) throw new ArgumentNullException("wait");

            var resourcesCompiled = resources.Compile();
            var ownerCompiled = owner.Compile();

            var scope = new LockScope
            {
                Resources = resourcesCompiled,
                Owner = ownerCompiled,
                Type = ScopeType.Lock,
                Start = _activities.Count
            };

            PushScope(scope);

            _activities.Add
            (
                AssignNameAndLocation
                (
                    new Lock
                    {
                        Resources = resourcesCompiled,
                        Owner = ownerCompiled,
                        Wait = wait.Compile(),
                        Interrupt = true,
                        Result = result
                    },
                    "BeginLock",
                    GetStatementIndex("BeginLock")
                )
            );
            return this;
        }

        #endregion

        #region Other

        public IProcessBuilder Breakpoint(string name)
        {
            if (name == null) throw new ArgumentNullException("name");

            if (!_emitBreakpoints)
            {
                return this;
            }

            Name(name);

            _activities.Add
            (
                AssignNameAndLocation
                (
                    new InterruptJump
                    {
                        Interruption = new Breakpoint { Name = name },
                        Position = _activities.Count + 1
                    },
                    "Breakpoint",
                    GetStatementIndex("Breakpoint")
                )
            );
            return this;
        }

        public IProcessBuilder Finish()
        {
            _activities.Add
            (
                AssignNameAndLocation
                (
                    new InterruptJump 
                    {
                        Interruption = Interruptions.Finish.Instance, 
                        Position = _activities.Count 
                    },
                    "Finish",
                    GetStatementIndex("Finish")
                )
            );
            return this;
        }

        #endregion
    }
}
